package animator.phantom.renderer.plugin;

/*
    Copyright Janne Liljeblad.

    This file is part of Phantom2D.

    Phantom2D is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Phantom2D is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Phantom2D.  If not, see <http://www.gnu.org/licenses/>.
*/

import animator.phantom.plugin.*;
import animator.phantom.renderer.param.*;
import animator.phantom.paramedit.*;
import com.jhlabs.image.*;
import giotto2D.filters.color.Desaturate;
import giotto2D.filters.render.SolidNoise;
import java.awt.image.*;
import java.awt.geom.*;
import java.awt.*;
import java.util.*;

public class LightRaysPlugin extends PhantomPlugin
{
	//--- ray generating
	public AnimatedValue centreX;
	public AnimatedValue centreY;
	public IntegerParam zoom;
	public IntegerParam pattern;
	public AnimatedValue patternStrength;
	public IntegerParam flickerSpeed;

	//--- Source in
	public AnimatedValue sourceLowLevel;
	public AnimatedValue sourceHighLevel;

	//--- ray out
	public AnimatedValue lowLevel;
	public AnimatedValue highLevel;
	public AnimatedValue lowOutputLevel;
	public AnimatedValue highOutputLevel;

	//--- Colorinzing
	public IntegerParam patterColorMode;
	public ColorParam lightColor;
	public ColorParam darkColor;
	public ColorParam middleColor;
	public IntegerParam middleValue;

	public BooleanParam doRaysColorize;
	public IntegerParam raysValue;
	public ColorParam raysColor;
	public ColorParam blackColor;

	//--- output type
	public IntegerParam renderType;
	public AnimatedValue opacity;

	//--- consts 
	private static final String LEVELS = "Levels";
	private static final String RAYS = "Rays";
	private static final String COLORIZE = "Color";

	private static final int PLASMA = 0;
	private static final int PLASMA_COLOR = 1;
	private static final int PLASMA_HIGH_COLOR = 2;
	private static final int SOLID_NOISE = 3;
	private static final int SOLID_NOISE_TURBULENT = 4;
	private static final int DEFAULT_PATTERN = PLASMA;

	private static final int SOURCE_COLOR = 0;
	private static final int WHITE_COLOR = 1;
	private static final int COLOR_MAP_COLOR = 2;

	private static final float[] ZOOM_VALUES = { 0.1f, 0.2f, 0.3f, 0.6f, 1.1f, 2.2f, 4.5f, 9f, 18f, 32f };
	private static final float FLICKER_STEP_MULTIPLIER = 0.1f;//speed value is multiplied by this to get pixels per frame movement 

	public static final int alpha_mask = 0x00ffffff;
	public static final int alpha = 24;
	public static final int red = 16;
	public static final int green = 8;
	public static final int MAX_RGB = 3 * 255;

	//--- pattern cache
	private BufferedImage patternImage = null;
	private int cachedType = DEFAULT_PATTERN;

	public LightRaysPlugin()
	{
		initPlugin( FILTER );
	}

	public void buildDataModel()
	{
		setName( "LightRays" );

		centreX = new AnimatedValue(  0.5f, 0, 1 );
		centreY = new AnimatedValue( 0.5f, 0, 1 );
		zoom = new IntegerParam( 3, 0, 6 );
		pattern = new IntegerParam( 0 );
		flickerSpeed = new IntegerParam( 0, -100, 100 );
		patternStrength =  new AnimatedValue( 100, 0, 100 );
 
		sourceLowLevel = new AnimatedValue( 0.0f, 0.0f, 255.0f );
		sourceHighLevel= new AnimatedValue( 255.0f, 0.0f, 255.0f );

		lowLevel = new AnimatedValue( 0.0f, 0.0f, 255.0f );
		highLevel = new AnimatedValue( 255.0f, 0.0f, 255.0f );
		lowOutputLevel = new AnimatedValue( 0.0f, 0.0f, 255.0f );
		highOutputLevel = new AnimatedValue( 255.0f, 0.0f, 255.0f );

		patterColorMode = new IntegerParam( SOURCE_COLOR );
		lightColor = new ColorParam( Color.white );
		darkColor = new ColorParam( new Color( 204, 0, 51 ) );
		middleColor = new ColorParam( new Color( 255, 153, 51 ) );
		middleValue = new IntegerParam( 110 );

		raysColor = new ColorParam( Color.green );
		raysValue = new IntegerParam( 130 );
		blackColor = new ColorParam( Color.black );
		doRaysColorize = new BooleanParam( false );

		renderType = new IntegerParam( 0 );
		opacity = new AnimatedValue( 100, 0, 100 );

		registerParameter( centreX );
		registerParameter( centreY );
		registerParameter( zoom );
		registerParameter( pattern );
		registerParameter( flickerSpeed );
		registerParameter( patternStrength );

		registerParameter( sourceLowLevel );
		registerParameter( sourceHighLevel );

		registerParameter( lowLevel );
		registerParameter( highLevel );
		registerParameter( lowOutputLevel );
		registerParameter( highOutputLevel );

		registerParameter( patterColorMode );
		registerParameter( lightColor );
		registerParameter( darkColor );
		registerParameter( middleColor );
		registerParameter( middleValue );

		registerParameter( blackColor );
		registerParameter( raysColor );
		registerParameter( raysValue );
		registerParameter( doRaysColorize );

		registerParameter( renderType );
		registerParameter( opacity );
	}

	public void buildEditPanel()
	{
		AnimValueNumberEditor centreXE = new AnimValueNumberEditor( "Centre X", centreX );
		AnimValueNumberEditor centreYE = new AnimValueNumberEditor( "Centre Y", centreY );
		IntegerValueSliderEditor zoomE = new  IntegerValueSliderEditor( "Rays length", zoom );
		AnimValueSliderEditor patternStE = new AnimValueSliderEditor( "Pattern Strength", patternStrength );
		String[] poptions = {"Plasma Low Color", "Plasma Mid Color", "Plasma High Color", "Noise", "Noise Turbulent" };
		IntegerComboBox patternE = new IntegerComboBox( pattern, "Pattern", poptions );
		IntegerValueSliderEditor flickerE = new  IntegerValueSliderEditor( "Flicker speed", flickerSpeed );
 		AnimValueNumberEditor opacityE = new AnimValueNumberEditor( "Opacity", opacity );

		AnimValueSliderEditor sourcelow = new AnimValueSliderEditor( "Source In low", sourceLowLevel );
		AnimValueSliderEditor sourcehigh = new AnimValueSliderEditor( "Source In high", sourceHighLevel );

		AnimValueSliderEditor low = new AnimValueSliderEditor( "Rays In low", lowLevel );
		AnimValueSliderEditor high = new AnimValueSliderEditor( "Rays In high", highLevel );
		AnimValueSliderEditor lowOut = new AnimValueSliderEditor("Rays Out low", lowOutputLevel );
		AnimValueSliderEditor highOut = new AnimValueSliderEditor( "Rays Out high", highOutputLevel );

		String[] pColOptions = {"Source", "White", "Color Map" };
		IntegerComboBox patternColE = new IntegerComboBox( patterColorMode, "Pattern Color", pColOptions );


		ParamColorSelect lightEditor = new ParamColorSelect( lightColor, "Colormap Light" );
		ParamColorSelect darkEditor = new ParamColorSelect( darkColor, "Colormap Dark" );
		ParamColorSelect middleEditor = new ParamColorSelect( middleColor, "Colormap Middle" );
		IntegerNumberEditor mVal = new IntegerNumberEditor( "Colornap Middle value", middleValue );

		ParamColorSelect raysColEditor = new ParamColorSelect( raysColor, "Rays Color" );
		IntegerNumberEditor raysValE = new IntegerNumberEditor( "Rays Color value", raysValue );
		ParamColorSelect blackEditor = new ParamColorSelect( blackColor, "Rays Black Color" );
		CheckBoxEditor doColE = new CheckBoxEditor( doRaysColorize, "Colorize rays", true );

		Vector<String> paneNames = new Vector<String>();
		paneNames.add( RAYS );
		paneNames.add( LEVELS );
		paneNames.add( COLORIZE );

		setTabbedPanel( 285, paneNames );

		addToTab(RAYS, zoomE  );
		addToTab(RAYS, new RowSeparator() );
		addToTab(RAYS, patternE );
		addToTab(RAYS, new RowSeparator() );
		addToTab(RAYS, patternStE );
		addToTab(RAYS, new RowSeparator() );
		addToTab(RAYS, flickerE );
		addToTab(RAYS, new RowSeparator() );
		addToTab(RAYS, centreXE );
		addToTab(RAYS, new RowSeparator() );
		addToTab(RAYS, centreYE );
		addToTab(RAYS, new RowSeparator() );
		addToTab(RAYS, opacityE );

		addToTab(LEVELS, sourcelow );
		addToTab(LEVELS, new RowSeparator() );
		addToTab(LEVELS, sourcehigh );
		addToTab(LEVELS, new RowSeparator() );
		addToTab(LEVELS, low  );
		addToTab(LEVELS, new RowSeparator() );
		addToTab(LEVELS, high );
		addToTab(LEVELS, new RowSeparator() );
		addToTab(LEVELS, lowOut );
		addToTab(LEVELS, new RowSeparator() );
		addToTab(LEVELS, highOut );

		addToTab(COLORIZE, patternColE );
		addToTab(COLORIZE, new RowSeparator() );
		addToTab(COLORIZE, lightEditor );
		addToTab(COLORIZE, new RowSeparator() );
		addToTab(COLORIZE, mVal );
		addToTab(COLORIZE, new RowSeparator() );
		addToTab(COLORIZE, middleEditor  );
		addToTab(COLORIZE, new RowSeparator() );
		addToTab(COLORIZE, darkEditor  );
		addToTab(COLORIZE, new RowSeparator() );

		addToTab(COLORIZE, doColE );
		addToTab(COLORIZE, new RowSeparator() );
		addToTab(COLORIZE, raysValE );
		addToTab(COLORIZE, new RowSeparator() );
		addToTab(COLORIZE, raysColEditor  );
		addToTab(COLORIZE, new RowSeparator() );
		addToTab(COLORIZE, blackEditor  );

		String[] roptions = {"Source image", "black background" };
		IntegerComboBox rtypeE = new IntegerComboBox( renderType, "Output on", roptions );
		addEditor( rtypeE );
	}

	public void doImageRendering( int frame )
	{
		BufferedImage flowImg = getFlowImage();
		BufferedImage raysImg = PluginUtils.getImageClone( flowImg );

		//--- Source levels
		LevelsFilter levelsFilter = new LevelsFilter();
		if( !( sourceLowLevel.getValue( frame ) == 0.0f && sourceHighLevel.getValue( frame ) == 255.0f )) // do only if necessary
		{
			levelsFilter.setLowLevel( sourceLowLevel.getValue( frame ) / 255.0f );
			levelsFilter.setHighLevel( sourceHighLevel.getValue( frame ) / 255.0f );
			Graphics2D gc = raysImg.createGraphics();
			gc.drawImage( raysImg, levelsFilter, 0, 0 );
			gc.dispose();
		}

		//--- Desaturate if source color not used
		if( patterColorMode.get() == COLOR_MAP_COLOR || patterColorMode.get() == WHITE_COLOR )
			Desaturate.filter( raysImg );

		//--- Pattern
		BufferedImage pImg = getPatternImage( frame );
		//--- Colorize pattern
		if( patterColorMode.get() == COLOR_MAP_COLOR )
		{
			ArrayColormap patternCM = getPatternColorMap();
			colorize( pImg, patternCM );
		}
		PluginUtils.doAlignedBlend( raysImg, pImg, patternStrength.get( frame ) / 100.0f, PluginUtils.MULTIPLY );

		//--- Zoom rays
		RaysBlurOp mbFilter = new RaysBlurOp();
		mbFilter.setCentreX( centreX.getValue( frame ) );
		mbFilter.setCentreY( centreY.getValue( frame ) );
		//mbFilter.setDistance( 10 );
		float zoomValue = ZOOM_VALUES[ zoom.get() ];
		mbFilter.setZoom( zoomValue );
		PluginUtils.filterImage( raysImg, mbFilter );

		//--- Ray levels
   		levelsFilter.setLowLevel( lowLevel.getValue( frame ) / 255.0f );
		levelsFilter.setHighLevel( highLevel.getValue( frame ) / 255.0f );
		levelsFilter.setLowOutputLevel( lowOutputLevel.getValue( frame ) / 255.0f );
		levelsFilter.setHighOutputLevel( highOutputLevel.getValue( frame ) / 255.0f );
		Graphics2D gc = raysImg.createGraphics();
		gc.drawImage( raysImg, levelsFilter, 0, 0 );
		gc.dispose();

		//--- Colorize rays
		if( doRaysColorize .get() == true )
		{
			ArrayColormap raysCM = getRaysColorMap();
			colorize( raysImg, raysCM );
		}

		//--- Combine rays with flow source
		PluginUtils.doAlignedBlend( flowImg, raysImg, opacity.get( frame ) / 100.0f, PluginUtils.SCREEN );

		//--- Send result
		sendFilteredImage( flowImg, frame );
	}

	private BufferedImage getPatternImage( int frame )
	{
		if( patternImage == null || cachedType != pattern.get() )
			createPatternImage();

		if( flickerSpeed.get() == 0 )
		{
			return patternImage;
		}
		else
		{
			float width = (float) patternImage.getWidth();
			float pos = (float)frame * (float) flickerSpeed.get() * FLICKER_STEP_MULTIPLIER;
			float modPos = pos % (2.0f * width);
			float pos1 = modPos;

			if( pos1 < -width)
				pos1 = pos1 + 2.0f * width;
			if( pos1 > width)
				pos1 = pos1 - 2.0f * width;

			float pos2 = 1;
			if( pos1 < 0 )
				pos2 = pos1 + width;
			else
				pos2 = pos1 - width;
			BufferedImage movedPattern = PluginUtils.createCanvas( patternImage.getWidth(), patternImage.getHeight() );
			BufferedImage flippedPattern = PluginUtils.createCanvas( patternImage.getWidth(), patternImage.getHeight() );

			//--- Mirror pattern 
			Graphics2D gm = flippedPattern.createGraphics();
			AffineTransform aMirror = new AffineTransform();
			//aMirror.setToTranslation( (double) -width, 0 );
			aMirror.scale( -1.0, 1.0 );
			aMirror.translate( (double) -width, 0 );
			gm.drawRenderedImage( patternImage, aMirror );
			gm.dispose();

			//--- Moved pattern
			Graphics2D gc = movedPattern.createGraphics();

			AffineTransform af1 = new AffineTransform();
			af1.setToTranslation( (double) pos1, 0 ); 
			gc.drawRenderedImage( patternImage, af1 );

			AffineTransform af2 = new AffineTransform();
			af2.setToTranslation( (double) pos2, 0 );
			gc.drawRenderedImage( flippedPattern, af2 );

			gc.dispose();

			return movedPattern;
		}
	}

	private void createPatternImage()
	{
		BufferedImage newPattern = PluginUtils.createScreenCanvas();
		int PLASMA_SEED = 42;//42 Chateau de Vaudeveaux

		PlasmaFilter psfilter = null;
		SolidNoise sn = null;
		switch( pattern.get() )
		{
			case PLASMA:
				psfilter = new PlasmaFilter();
				psfilter.setTurbulence( 0 );
				psfilter.setSeed( PLASMA_SEED );
				PluginUtils.filterImage( newPattern, psfilter );
				break;

			case PLASMA_COLOR:
				psfilter = new PlasmaFilter();
				psfilter.setTurbulence( 3.3f );
				psfilter.setSeed( PLASMA_SEED );
				PluginUtils.filterImage( newPattern, psfilter );
				break;

			case PLASMA_HIGH_COLOR:
				psfilter = new PlasmaFilter();
				psfilter.setTurbulence( 10.0f );
				psfilter.setSeed( PLASMA_SEED );
				PluginUtils.filterImage( newPattern, psfilter );
				break;

			case SOLID_NOISE:
				sn = new SolidNoise();
				sn.setDetail( 2 );
				sn.setXSize( 12 );
				sn.setYSize( 2 );
				sn.filter( newPattern );
				break;

			case SOLID_NOISE_TURBULENT:
				sn = new SolidNoise();
				sn.setTurbulence( true );
				sn.setDetail( 2 );
				sn.setXSize( 12 );
				sn.setYSize( 2 );
				sn.filter( newPattern );
		}

		patternImage = newPattern;
		cachedType = pattern.get();
	}

	private void colorize( BufferedImage img, ArrayColormap cmap )
	{

		int[] rbgLook = new int[256];
		for( int j = 0; j < 256; j++ )
			rbgLook[ j ] = cmap.getColor( (float) j / 255.0f ) & alpha_mask;

		//--- Do color replacement.
		int[] pix = getBank( img );
		int rbg;
		int lumaValue;
		int a;
		int r;
		int g;
		int b;

		for( int i = 0; i < pix.length; i++ )
		{
			a = ( pix[ i ] >> alpha ) & 0xff;
			r = ( pix[ i ] >> red ) & 0xff;
			g = ( pix[ i ] >> green ) & 0xff;
			b = ( pix[ i ] & 0xff );

			lumaValue = (( r + g + b ) * 255 ) / MAX_RGB;
			rbg = rbgLook[ lumaValue ];

			pix[ i ] =  a << alpha | rbg;
 		}
	}

	private ArrayColormap getPatternColorMap()
	{
		int mid = middleValue.get();
		ArrayColormap cmap = new ArrayColormap();
		cmap.setColorRange(0, mid, darkColor.get().getRGB(), middleColor.get().getRGB() );
		cmap.setColorRange(mid, 255, middleColor.get().getRGB(), lightColor.get().getRGB() );
		return cmap;
	}

	private ArrayColormap getRaysColorMap()
	{
		int mid = raysValue.get();
		ArrayColormap cmap = new ArrayColormap();
		cmap.setColorRange(0, mid, blackColor.get().getRGB(), raysColor.get().getRGB() );
		cmap.setColorRange(mid, 255, raysColor.get().getRGB(), raysColor.get().getRGB() );
		return cmap;
	}

	private static int[] getBank( BufferedImage img )
	{
		WritableRaster imgRaster = img.getRaster();
		DataBufferInt dbuf = (DataBufferInt) imgRaster.getDataBuffer();
		return dbuf.getData( 0 );
	}

}//end class
